--[[ 
  vector "class", a bunch of overloaded operators, really
  any list can be used as a vector
]]


Vector = {}


-- tags a table as a Vector, operators will work on this table in the future
function Vector.tag(list)
  return setmetatable(list, Vector)
end


-- clones the list part of a table and tags it as a vector
function Vector.clone(vector)
  local c = Vector{}
  for i,v in ipairs(vector) do
    c[i] = v
  end
  return c
end


-- applies a binary function to elements of two vectors, returns a new vector with the results
function Vector.binary(a,b,f)
  local r = Vector{}
  for i = 1, math.min(#a,#b) do
    r[i] = f(a[i],b[i])
  end
  return r
end


-- applies an unary function to elements of a vector, returns a new vector with the results
function Vector.unary(a,f)
  local r = Vector{}
  for i,c in ipairs(a) do
    r[i] = f(c)
  end
  return r
end


-- vector addition
function Vector.add(a,b)
  return Vector.binary(a,b, function(x,y) return x+y end)
end


-- vector subtraction
function Vector.sub(a,b)
  return Vector.binary(a,b, function(x,y) return x-y end)
end


-- unary negation
function Vector.unm(a)
  return Vector.unary(a, function(x) return -x end)
end


-- multiplication
-- we try to do it one way: a is the vector and b is the scalar
-- if it fails we try the other way around
function Vector.mul(a,b)
  local ok, r = pcall( function()
    return Vector.unary(a, function(x) return b*x end) 
  end )
  if ok then return r end
  return Vector.unary(b,function(x) return a*x end)
end


--division
function Vector.div(a,b)
  return Vector.unary(a, function(x) return x/b end) 
end


-- dot product
function Vector.dot(a,b)
  local s, m = 0, Vector.binary(a,b, function(x,y) return x*y end)
  for _,v in ipairs(m) do s = s + v end
  return s
end


-- equality test
function Vector.eq(a,b)
  if #a ~= #b then return false end
  for i=1, #a do
    if a[i] ~=b[i] then return false end
  end
  return true
end


-- pretty printing vectors
function Vector.tostring(a)
  local s = "{"
  if getmetatable(a) == Vector then
    Vector.binary(a,a, function(x,y) s = s.." "..Vector.tostring(x)..";" end )
    return s.." }"
  else return tostring(a) end
end

-- actual operator overloading
Vector.__add = Vector.add
Vector.__sub = Vector.sub
Vector.__unm = Vector.unm
Vector.__mul = Vector.mul
Vector.__div = Vector.div
Vector.__eq = Vector.eq


-- to enable the Vector{} syntax
setmetatable(Vector, { __call = function(v,p) return v.tag(p) end} )

-- simple tests
assert(Vector{})
assert(Vector{1,2,3} == Vector{1,2,3})
assert(Vector{1} ~= Vector{2})
assert(Vector{1,2} ~=Vector{1})
assert(Vector{0,1} + Vector{1,2} == Vector{1,3})
assert(Vector{3,3} - Vector{1,2} == Vector{2,1})
assert(2*Vector{1,2} == Vector{2,4})
assert(Vector{1,2}*2 == Vector{2,4})
assert( Vector.dot({1,0},{0,1}) == 0)
assert( Vector.dot({2,0},{2,0}) == 4)

